package jp.co.ledinge.syllabarygame;

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;

import android.content.Context;
import android.graphics.Rect;
import android.util.Log;

public class Recognize {

	private static final String TAG = "SyllabaryGame";
	/** 文字パターンマップ */
	private HashMap<String, Pattern> mPatternMap;
	/** 入力テキスト文字 */
	private String[] mCharStrs;
	private Context mContext;

	private static final int NORMALIZE_SIZE = 256;
	/** 多分100でいいと思う */
	private static final int SCALE = 1000;
	/** 変化点の角度 とりあえず10度でいいと思う */
	private static final int UNIFY_ANGLE = 10;
	/** 短いセグメントを統合する場合の値 とりあえず2にしておく */
	private static final int MIN_SEGLEN = 20;
	/** 16dot間隔 */
	private static final int SEG_PITCH = 64;
	private static final int FEATURE_SIZE = 10;

	public Recognize(HashMap<String, Pattern> readObject, String[] charstrs) {
		mPatternMap = readObject;
		mCharStrs = charstrs;
	}

	public Recognize(HashMap<String, Pattern> readObject, String[] charstrs, Context context) {
		mPatternMap = readObject;
		mCharStrs = charstrs;
		mContext = context;
	}
	public ArrayList<Pattern> recognize(BoxView boxView) {
		// 座標から有効点を取る
		Point[] samplePoints = boxView.samplingPoint();
		Point[] points = null;
		ArrayList<Pattern> patternList = new ArrayList<Pattern>();
		if (samplePoints.length == 0) {
			return patternList;
		}
		ArrayList<Stroke> strokes = changePoint2Stroke(samplePoints);
		points = normalizeChar(strokes);
		points = approximateByStraightLine(points);
		Pattern inPattern = unifyShortSegment(points);
		inPattern = approximateByConstantPitch(inPattern);

		for (String registStr : mCharStrs) {
			Pattern dstPattern = mPatternMap.get(registStr);
			if (dstPattern == null) {
				continue;
			}
			DPmatch dPmatch = new DPmatch();
			int distance = dPmatch.calcDistance(inPattern, dstPattern, 3000);
			dstPattern.setDistance(distance);
			patternList.add(dstPattern);
		}

		Collections.sort(patternList, new Comparator<Pattern>() {
			public int compare(Pattern t1, Pattern t2) {
				return t1.getDistance() - t2.getDistance();
			}
		});
		return patternList;
	}
	/**
	 * 座標からストローククラスを作る。
	 * 
	 * @param points
	 * @return
	 */
	public ArrayList<Stroke> changePoint2Stroke(Point[] points) {
		ArrayList<Point> tmpPoints = new ArrayList<Point>();
		ArrayList<Stroke> strokes = new ArrayList<Stroke>();
		for (Point point : points) {
			if (!Stroke.isStrokeEnd(point)) {
				tmpPoints.add(point);
			} else {
				int size = tmpPoints.size();
				if (size == 0) {
					continue;
				}
				tmpPoints.add(point); // PEN_UP
				strokes.add(new Stroke(tmpPoints.toArray(new Point[0])));
				tmpPoints = new ArrayList<Point>();
			}
		}
		return strokes;
	}

	/**
	 * 文字の外接枠計算 (一番大きい外枠を比較し続ける)
	 * 
	 * @param strokes
	 */
	public Rect extractBoundingBox(ArrayList<Stroke> strokes) {
		int count = strokes.size();
		Rect bx = null;

		for (int i = 0; i < count; i++) {
			Stroke strk = strokes.get(i);
			if (bx == null) {
				bx = new Rect(strk.boundingBox);
			} else {
				if (bx.left > strk.boundingBox.left) {
					bx.left = strk.boundingBox.left;
				}
				if (bx.right < strk.boundingBox.right) {
					bx.right = strk.boundingBox.right;
				}
				if (bx.top > strk.boundingBox.top) {
					bx.top = strk.boundingBox.top;
				}
				if (bx.bottom < strk.boundingBox.bottom) {
					bx.bottom = strk.boundingBox.bottom;
				}
			}
		}
		return bx;
	}

	/**
	 * 入力パターンを正規化 文字タイプに応じて以下の大きさに座標変換する.<br>
	 * 　　　ドット: 32dot x 32dot <br>
	 * 　　　横長 : 128dot x 32dot<br>
	 * 　　　縦長 : 32dot x 128dot<br>
	 * 　　　通常 : 128dot x 128dot<br>
	 * 
	 * @param strokes
	 *            文字のストロークの集まり（おそらく）
	 * @param boundingBox
	 *            外枠 extractBoundingBoxで求める
	 * @return 座標変換をした全ストロークのポイント
	 */
	public Point[] normalizeChar(ArrayList<Stroke> strokes) {
		Rect boundingBox = extractBoundingBox(strokes); // 多分これでいいと思う
		int count = 0;
		int numStrks = strokes.size(); // 多分これでいいと思う
		for (int i = 0; i < numStrks; i++) {
			count += strokes.get(i).points.length;
		}
		Point[] pts = new Point[count];

		int width = boundingBox.right - boundingBox.left + 1;
		int height = boundingBox.bottom - boundingBox.top + 1;
		int normWidth = NORMALIZE_SIZE;
		int normHeight = NORMALIZE_SIZE;
		// int type = checkCharType(boundingBox);
		// switch (type) {
		// case TYPE_DOT:
		// normWidth /= HV_RATIO;
		// normHeight /= HV_RATIO;
		// break;
		// case TYPE_LONGX:
		// normHeight /= HV_RATIO;
		// break;
		// case TYPE_LONGY:
		// normWidth /= HV_RATIO;
		// break;
		// }
		int xscale = normWidth * SCALE / width;
		int yscale = normHeight * SCALE / height;
		Log.v("norm", "xscale=" + xscale + ", yscale=" + yscale);
		int idx = 0;
		for (int i = 0; i < numStrks; i++) {
			Stroke strk = strokes.get(i);
			for (int j = 0; j < strk.points.length; j++) {
				Point po = new Point(strk.points[j]);
				if (Stroke.isStrokeEnd(po) == false) {
					po.x = (po.x - boundingBox.left) * xscale / SCALE;
					po.y = (po.y - boundingBox.top) * yscale / SCALE;
				}
				pts[idx++] = po;
				Log.v("norm", "x=" + po.x + ", y=" + po.y);
			}
		}
		return pts;
	}

	// 折れ線近似を行う
	/**
	 * 変化点抽出 ストロークの座標点間の角度を求め,　座標点の前後のセグメントの角度差が大きい(10度以上)座標点を変化点として抽出する.
	 * セグメントの長さが短い場合, 前後のセグメントに統合する.
	 * 
	 * @param points
	 */
	public Point[] approximateByStraightLine(Point[] points) {
		int count = points.length;
		Point[] pts = new Point[count];
		System.arraycopy(points, 0, pts, 0, count);
		Point p0 = Stroke.setStrokeEnd();
		int idx = 0;
		int curr = 0;
		for (int i = 0; i < count; i++) {
			if (Stroke.isStrokeEnd(p0) == true) {
				pts[idx].x = pts[i].x; // 始点を保存
				pts[idx++].y = pts[i].y;
				p0 = pts[i];
				curr = i;
				i++;
			} else if (Stroke.isStrokeEnd(pts[i]) == true) {
				pts[idx].x = pts[i - 1].x; // 終点を保存
				pts[idx++].y = pts[i - 1].y;
				pts[idx].x = pts[i].x; // PenUpを保存
				pts[idx++].y = pts[i].y;
				p0 = pts[i];
				curr = 1;
			} else {
				int maxDiff = 0;
				int maxIdx = 0;
				for (int j = curr + 1; j < i; j++) {
					Segment seg02 = new Segment(p0, pts[j]);
					Segment seg21 = new Segment(pts[j], pts[i]);
					int diff = seg02.calcDiffDirect(seg21);
					if (diff >= maxDiff) {
						maxDiff = diff;
						maxIdx = j;
					}
				}
				if (maxDiff > UNIFY_ANGLE) { // 変化点
					p0 = pts[maxIdx];
					Log.v("sample", "idx=" + maxIdx + "x=" + p0.x + ", y="
							+ p0.y + ", dir=" + maxDiff);
					pts[idx].x = p0.x;
					pts[idx++].y = p0.y;
					curr = maxIdx;
					i = curr + 1;
				}
			}
		}
		return reallocPoint(pts, idx);
	}

	/**
	 * 短いセグメントを統合する
	 * 
	 * @param pts
	 * @return
	 */
	public Pattern unifyShortSegment(Point[] pts) {
		Segment[] segs = new Segment[pts.length];

		// セグメント情報, ストロークのON/OFFを抽出する.
		int idx = 0;
		int count = pts.length;
		for (int i = 1; i < count; i++) {
			if (!Stroke.isStrokeEnd(pts[i])) {
				segs[idx] = new Segment(pts[idx], pts[i], Segment.PEN_DOWN);
				pts[idx + 1] = pts[i];
				idx++;
			} else { // ストローク終点
				if (++i < count) {
					segs[idx] = new Segment(pts[idx], pts[i], Segment.PEN_UP);
					pts[idx + 1] = pts[i];
					idx++;
				}
			}
		}

		// 短いセグメントを前後のセグメントに統合
		count = idx;
		idx = 0;
		for (int i = 0; i < count; i++) {
			int diff1 = 180;
			int diff2 = 180;
			if (segs[i].length < MIN_SEGLEN && segs[i].isPenDown()) {
				if (i > 0 && segs[i - 1] != null && segs[i - 1].isPenDown()) {
					diff1 = segs[i - 1].calcDiffDirect(segs[i]);
				}
				if (segs[i + 1] != null && segs[i + 1].isPenDown()) {
					diff2 = segs[i].calcDiffDirect(segs[i + 1]);
				}
				if (diff1 <= diff2) {
					if (idx > 0 && i > 0 && (i + 1) < count) {
						segs[idx - 1].direct = Segment.calcDirect(pts[i - 1],
								pts[i + 1]);
						segs[idx - 1].length = Segment.calcLength(pts[i - 1],
								pts[i + 1]);
					}
				} else {
					if ((i + 2) < count) {
						segs[i + 1].direct = Segment.calcDirect(pts[i],
								pts[i + 2]);
						segs[i + 1].length = Segment.calcLength(pts[i],
								pts[i + 2]);
						pts[i + 1] = pts[i];
					}
				}
			} else {
				segs[idx] = segs[i];
				pts[idx++] = pts[i];
			}
		}
		// 最後の座標点をコピー
		pts[idx] = pts[count];

		// 配列ｻｲｽﾞを変更
		Point[] points = reallocPoint(pts, idx + 1);
		Segment[] segments = reallocSegment(segs, idx);
		Pattern pattern = new Pattern(points, segments);
		return pattern;
	}

	/**
	 * 入力パターンの作成 ストロークを構成するセグメントとストローク間の移動部分を一定(今回は16dot)の間隔で近似する.
	 * 
	 * @return
	 */
	// 一定ピッチで近似する
	public Pattern approximateByConstantPitch(Pattern pattern) {
		int bufSize = FEATURE_SIZE;
		Point[] patPts = new Point[bufSize];
		Segment[] patSegs = new Segment[bufSize];
		Point[] pts = pattern.points;
		Segment[] segs = pattern.segments;
		int count = pts.length;

		int idx = 0;
		int base = 0;
		patPts[idx++] = new Point(pts[base]); // セグメントの始点をセット
		for (int i = 0; i < count - 1; i++) {
			int segnum = segs[i].length / SEG_PITCH;
			if ((segs[i].length % SEG_PITCH) >= MIN_SEGLEN)
				segnum++;
			if (segnum == 0)
				segnum = 1;
			int seglen = segs[i].length / segnum;
			if (segnum > 1) {
				int sumLen = seglen;
				for (int j = 0; j < segnum - 1; j++) {
					int dx = ((pts[i + 1].x - pts[i].x) * sumLen)
							/ segs[i].length;
					int dy = ((pts[i + 1].y - pts[i].y) * sumLen)
							/ segs[i].length;
					patPts[idx] = new Point((dx + patPts[base].x),
							(dy + patPts[base].y));
					patSegs[idx - 1] = new Segment(patPts[idx - 1],
							patPts[idx], segs[i].state);
					if (++idx >= bufSize) {
						bufSize += FEATURE_SIZE;
						patPts = reallocPoint(patPts, bufSize);
						patSegs = reallocSegment(patSegs, bufSize);
					}
					sumLen += seglen;
				}
			}
			// セグメントの終点
			patPts[idx] = new Point(pts[i + 1]); // セグメントの始点をセット
			patSegs[idx - 1] = new Segment(patPts[idx - 1], patPts[idx],
					segs[i].state);
			base = idx;
			if (++idx >= bufSize) {
				bufSize += FEATURE_SIZE;
				patPts = reallocPoint(patPts, bufSize);
				patSegs = reallocSegment(patSegs, bufSize);
			}
		}
		Point[] points = reallocPoint(patPts, idx);
		Segment[] segments = reallocSegment(patSegs, idx - 1);
		return new Pattern(points, segments);
	}

	private Point[] reallocPoint(Point[] pts, int size) {
		Point[] point = new Point[size];
		if (pts.length < size) {
			size = pts.length;
		}
		System.arraycopy(pts, 0, point, 0, size);
		return point;
	}

	private Segment[] reallocSegment(Segment[] segs, int size) {
		Segment[] segments = new Segment[size];
		if (segs.length < size) {
			size = segs.length;
		}
		System.arraycopy(segs, 0, segments, 0, size);
		return segments;
	}
	/**
	 * データのセーブ
	 * 
	 * @param view
	 */
	public void save() {
		try {
		    FileOutputStream fos = mContext.openFileOutput("SaveData.dat", Context.MODE_WORLD_READABLE);
		    ObjectOutputStream oos = new ObjectOutputStream(fos);
		    oos.writeObject(mPatternMap);
		    oos.close();
		} catch (Exception e) {
		    Log.d(TAG, "Error");
		}
	}
	public void regist(String nowStr, Pattern pattern) {
		mPatternMap.put(nowStr, pattern); //上書き
	}


}
